/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/graphics/dx11.1/details/common.hpp>
#include <baja/graphics/dx11.1/graphics_fwd.hpp>
#include <baja/graphics/dx11.1/details/enum_mappings.hpp>
#include <baja/graphics/dx11.1/details/util.hpp>

namespace baja { namespace graphics { namespace dx11_1 {

namespace d = baja::graphics::dx11_1::details;

// vertex_buffer implementation
class vertex_buffer : public ivertex_buffer, public b::iunknown_provider
{
private:
    WRL::ComPtr<ID3D11Buffer> _vertexBuffer;
    std::vector<memory> _data;
    std::weak_ptr<iengine> _graphics;
    
public:
    vertex_buffer(ID3D11Buffer*  vertexBuffer, const std::shared_ptr<iengine>& graphics) : _vertexBuffer(vertexBuffer), _graphics(graphics)  {}

    static std::shared_ptr<vertex_buffer> create(ID3D11Buffer* vertexBuffer, const std::shared_ptr<iengine>& graphics)
    {
        std::shared_ptr<vertex_buffer> returnValue = std::make_shared<vertex_buffer>(vertexBuffer, graphics);
        return returnValue;
    }
    
    // b::iunknown_provider

    virtual size_t getInterfaceCount()
    {
        return 1;
    }

    virtual WRL::ComPtr<IUnknown> getInterface(size_t index)
    {
        BAJA_CHECK_ARG(index == 0);
        WRL::ComPtr<IUnknown> returnValue;
        _vertexBuffer.As(&returnValue);
        return returnValue;
    }

    // Locks the vertex buffer for write access
    void lockDiscard(memory** data, const uint32 context = 0)
    {
        BAJA_CHECK_ARG(data);
        
        std::shared_ptr<engine> graphicsImpl = std::dynamic_pointer_cast<engine>(_graphics.lock());
        WRL::ComPtr<ID3D11DeviceContext> deviceContext = graphicsImpl->getContext(context);

        win32_result wr;

        D3D11_BUFFER_DESC desc;
        _vertexBuffer->GetDesc(&desc);
        if (desc.Usage == D3D11_USAGE_DYNAMIC)
        {
            memory* mappedData = NULL;

            D3D11_MAPPED_SUBRESOURCE mappedResource;
            wr = deviceContext->Map(
                _vertexBuffer.Get(),
                0,
                D3D11_MAP_WRITE_DISCARD,
                0,
                &mappedResource
                );

                mappedData = (memory*)mappedResource.pData;

            *data = mappedData;
        }
        else
        {
            if (_data.size() > 0) wr = E_FAIL; // ERROR:
            _data.resize(desc.ByteWidth);
            *data = _data.data();
        }
    }
    
    // Locks the vertex buffer for write access
    void lockNoOverwrite( 
        const uint32 offset,
        const uint32 sizeToLock,
        memory** data,
        const uint32 context = 0
        )
    {
        BAJA_CHECK_ARG(data);

        std::shared_ptr<engine> graphicsImpl = std::dynamic_pointer_cast<engine>(_graphics.lock());
        WRL::ComPtr<ID3D11DeviceContext> deviceContext = graphicsImpl->getContext(context);
        win32_result wr;

        // only dynamic buffers can be locked as no overwrite
        D3D11_BUFFER_DESC desc;
        _vertexBuffer->GetDesc(&desc);
        if (desc.Usage != D3D11_USAGE_DYNAMIC) wr = E_FAIL; // error

        // ensure area to lock is valid
        if (offset >= desc.ByteWidth) wr = E_INVALIDARG;
        if (offset + sizeToLock > desc.ByteWidth) wr = E_INVALIDARG;

        // map the buffer for access to its memory
        memory* mappedData = nullptr;
        D3D11_MAPPED_SUBRESOURCE mappedResource;
        wr = deviceContext->Map(
            _vertexBuffer.Get(),
            0,
            D3D11_MAP_WRITE_NO_OVERWRITE,
            0,
            &mappedResource);

        mappedData = (memory*)mappedResource.pData;

        *data = mappedData + offset;
    }
    
    // Unlocks the vertex buffer
    void unlock(const uint32 context = 0)
    {
        std::shared_ptr<engine> graphicsImpl = std::dynamic_pointer_cast<engine>(_graphics.lock());
        WRL::ComPtr<ID3D11DeviceContext> deviceContext = graphicsImpl->getContext(context);

        D3D11_BUFFER_DESC desc;
        _vertexBuffer->GetDesc(&desc);
        if (desc.Usage == D3D11_USAGE_DYNAMIC)
        {
            deviceContext->Unmap(_vertexBuffer.Get(), 0);
        }
        else
        {
            win32_result wr;
            if (_data.size() == 0) wr = E_FAIL; // ERROR:
                
            deviceContext->UpdateSubresource(
                _vertexBuffer.Get(),
                0,
                NULL,
                _data.data(),
                0,
                0);

            _data.clear();
        }
    }
    
    // Gets the size, in bytes, of this vertex buffer
    uint32 getSize()
    {
        D3D11_BUFFER_DESC desc;
        _vertexBuffer->GetDesc(&desc);
        return desc.ByteWidth;
    }
};

// Creates a vertex buffer
inline std::shared_ptr<ivertex_buffer>
engine::createVertexBuffer( 
    const uint32 totalBytes,
    const vertex_buffer_usage usage,
    const std::shared_ptr<iengine>& graphics
    )
{ 
    BAJA_CHECK_ARG(totalBytes > 0);

    D3D11_BUFFER_DESC vbDesc;
    vbDesc.ByteWidth = totalBytes;
    vbDesc.Usage = d::toD3D11_USAGE(usage);
    vbDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vbDesc.CPUAccessFlags = (vbDesc.Usage == D3D11_USAGE_DYNAMIC ? D3D11_CPU_ACCESS_WRITE : 0);
    vbDesc.MiscFlags = 0;
    vbDesc.StructureByteStride = 0;

    win32_result wr;

    WRL::ComPtr<ID3D11Buffer> vb;
    wr = _d3d11Device->CreateBuffer(&vbDesc, NULL, vb.GetAddressOf());

    return vertex_buffer::create(vb.Get(), graphics);
}

}}} // namespace baja.graphics.dx11_1
